<?php

declare(strict_types=1);

namespace App\Controller\Api\V2\Orders;

use App\Constants\Order\OrderCode;
use App\Constants\Payment\PaymentCode;
use App\Controller\Api\V2\Controllers;
use App\Exception\InternalException;
use App\Factory\Wechat\Factory;
use App\Model\Orders\Invoice;
use App\Services\Orders\Order as OrderServices;
use App\Services\Payment\Payment;
use App\Utils\Other\Safepay\Refund\Mini;
use Hyperf\Collection\Arr;
use Hyperf\Context\Context;
use Hyperf\DbConnection\Db;
use Psr\Http\Message\ResponseInterface;
use App\Request\Orders\Order as OrderRequest;
use App\Services\Orders\OrderAction as OrderActionServices;
use App\Services\Orders\Invoice as InvoiceServices;
use App\Services\Logistics\Logistic as LogisticServices;
use function Hyperf\Translation\__;
use function Hyperf\Support\make;
use Hyperf\RateLimit\Annotation\RateLimit;

/**
 * Class Order
 * @author zx
 * @package App\Controller\Api\V2\Orders
 */
class Order extends Controllers
{
    /**
     * 请求类
     * @var string
     */
    protected string $requestClass = OrderRequest::class;

    public function __construct(
        OrderServices $orderServices,
        protected OrderActionServices $orderActionServices,
        protected InvoiceServices $invoiceServices,
        protected LogisticServices $logisticServices,
        protected Factory $wechatFactory,
        protected Payment $paymentServices
    )
    {
        parent::__construct();
        $this->services = $orderServices;
    }

    /**
     * 新增
     * @return ResponseInterface
     * @throws \EasyWeChat\Kernel\Exceptions\BadResponseException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
     */
    public function save():ResponseInterface
    {
        $input = $this->input([
            'merchant_no', 'postage_amount', 'consignee', 'mobile', 'prov',
            'city', 'area', 'address', 'goods', 'discount_amount',
            'paid_amount', 'user_note', 'time_expire', 'attach',
            'source' => 'mini', 'jump' => true, 'open_id', 'appid', 'payer',
            'auto_erp'
        ]);

        /** 继续支付 */
        if (
            Arr::get($input, 'jump') == false &&
            $orderModel = $this->services->where('merchant_no', $input['merchant_no'])->first()
        ) {

            /** @var \App\Factory\Payment\Factory $factory */
            $factory = make(\App\Factory\Payment\Factory::class);
            $param = [
                'out_trade_no' => $orderModel->no,
                'payMethon' => 'NeoPay',
                'amount' => $orderModel->paid_amount,
                'description' => "跨境订单支付:{$orderModel->no}",
                'storeName' => $orderModel->shop->name,
                'openid' => $orderModel->payer['openid']
            ];

            [$request, $response, $responseParams] = $factory->safepayMini($param, $orderModel->payer['app_id']);

            $this->paymentServices->create([
                'order_no' => $orderModel->no,
                'amount' => $param['amount'],
                'no' => $response['data']['trace'],
                'user_id' => $orderModel->user_id,
                'request_body' => $request,
                'response_body' => $response,
                'type' => PaymentCode::TYPE_PAYMENT,
                'code' => 'safepayMini'
            ]);
            $orderModel->fill(['paid_no' => $response['data']['trace']])->save();

            return success(['no' => $orderModel->no, 'body' => $responseParams]);
        }

        if ($this->services->where('merchant_no', $input['merchant_no'])->exists()) {
            return failed(__('message.model_found'));
        }

        if (Arr::get($input, 'discount_amount')) {
            foreach ($input['discount_amount'] as $key => $value) {
                $keys = ['coupon', 'discount', 'integral'];
                if (!in_array($key, $keys)) {
                    return failed(__('message.discount_amount.key.not_in', [
                        'key' => $key
                    ]));
                }
            }
        }

        $data = Db::transaction(function () use ($input) {
            /** @var \App\Model\Orders\Order $orderModel */
            $orderModel = $this->services->setOrder($input);
            $orderNo = $orderModel->no;
            $data = [];
            switch ($input['source']) {
                case OrderCode::SOURCE_MINI:
                    if (!$input['jump']) {
                        /** @var \App\Factory\Payment\Factory $factory */
                        $factory = make(\App\Factory\Payment\Factory::class);
                        $param = [
                            'out_trade_no' => $orderModel->no,
                            'payMethon' => 'NeoPay',
                            'amount' => $orderModel->paid_amount,
                            'description' => "跨境订单支付:{$orderModel->no}",
                            'storeName' => $orderModel->shop->name,
                            'openid' => $orderModel->payer['openid']
                        ];

                        [$request, $response, $responseParams] = $factory->safepayMini($param, $orderModel->payer['app_id']);

                        $this->paymentServices->create([
                            'order_no' => $orderModel->no,
                            'amount' => $param['amount'],
                            'no' => $response['data']['trace'],
                            'user_id' => $orderModel->user_id,
                            'request_body' => $request,
                            'response_body' => $response,
                            'type' => PaymentCode::TYPE_PAYMENT,
                            'code' => 'safepayMini'
                        ]);
                        $orderModel->fill(['paid_no' => $response['data']['trace']])->save();

                        return ['no' => $orderNo, 'body' => $responseParams];
                    }
                    $data = ['no' => $orderNo];
                    break;
                case OrderCode::SOURCE_PC:
                    $app = $this->wechatFactory->mini();
//                $response = $app->getClient()->postJson('/wxa/getwxacodeunlimit', [
//                    'scene' => "order_sn={$orderNo}",
//                    'page' => 'pages/index/index'
//                ]);
//                $path = BASE_PATH.'/public/static/images/qrcode/'.date('YmdHis').'/';
//                if (!is_dir($path)) {
//                    mkdir($path, 0777, true);
//                }
//                $filename = md5(static::getAppId().Coroutine::id()).'.png';
//                $response->saveAs($path.$filename);
//                $url = getDomain().str_replace(BASE_PATH.'/public', '', $path).$filename;
//                $data = ['no' => $orderNo, 'qrcode' => $url];
                    $response = $app->getClient()->post('/wxa/generate_urllink', [
                        'json' => [
                            'query' => "order_sn={$orderNo}",
                            'path' => 'pages/index/index'
                        ]
                    ])->toArray();
                    if (Arr::get($response, 'errcode')) {
                        throw new InternalException($response['errmsg']);
                    }
                    $data = ['no' => $orderNo, 'url_link' => $response['url_link']];
                    break;
            }

            return $data;
        });

        return success($data);
    }

    /**
     * 详情
     * @return ResponseInterface
     * @throws \Throwable
     */
    public function read():ResponseInterface
    {
        $input = $this->input(['no', 'merchant_no']);

        /** @var \App\Model\Orders\Order $orderModel */
        $info = $this->services
            ->search($input)
            ->with(['invoice'])
            ->firstOrFail()
            ->makeHidden([
                'id',
                'shop_id',
                'app_id',
                'user_id',
                'payer_name',
                'payer_no',
                'payer_mobile',
                'payer',
                'updated_at'
            ])
            ->append([
                'status_text',
                'pay_status_text'
            ]);

        $info->invoice = $info->invoice->map(function ($v) {
            return $v->makeHidden([
                'id',
                'order_id',
                'shop_id',
                'type'
            ]);
        });

        return success(compact('info'));
    }

    /**
     * 退款
     * @return ResponseInterface
     * @throws \Throwable
     */
    #[RateLimit(create:20, capacity:20, key:[Order::class, "getAppId"], waitTimeout:1)]
    public function refund():ResponseInterface
    {
        $input = $this->input(['no', 'merchant_no', 'refund_no', 'user_note', 'consignee', 'mobile']);
        $search = $this->input(['no', 'merchant_no']);
        /** @var \App\Model\Orders\Order $orderModel */
        $orderModel = $this->services->search($search)->firstOrFail();
        if (in_array($orderModel->pay_status, [
            OrderCode::PAY_STATUS_REFUND,
            OrderCode::PAY_STATUS_PENDING_PAID,
            OrderCode::PAY_STATUS_REFUSE_REFUND,
            OrderCode::PAY_STATUS_APPLY_REFUND
            ])) {
            return failed(__('message.order.status.failed'));
        }
        if (!in_array($orderModel->status, [
            OrderCode::STATUS_PENDING_DELIVERY,
            OrderCode::STATUS_PENDING_RECEIVED,
            OrderCode::STATUS_RECEIVED,
            OrderCode::STATUS_COMPLETE
        ])) {
            return failed(__('message.order.status.no_refund'));
        }

        $paymentModel = $orderModel->payment()
            ->where('type', PaymentCode::TYPE_PAYMENT)
            ->whereNotNull('process_at')
            ->firstOrFail();

        Db::transaction(function () use ($orderModel, $paymentModel, $input) {
            $refundModel = $orderModel->payment()->create([
                'user_id' => $orderModel->user_id,
                'amount' => $orderModel->paid_amount,
                'type' => PaymentCode::TYPE_REFUND,
                'code' => $paymentModel->code,
                'no' => $input['refund_no']
            ]);
            if (!$refundModel->exists) {
                throw new InternalException();
            }
            $params = [
                'refund_no' => $refundModel->no,
                'pay_order_no' => $orderModel->paid_no,
                'remark' => $input['user_note'],
                'refund_amount' => $orderModel->paid_amount
            ];
            /** @var Mini $refundFactory */
            $refundFactory = make(Mini::class);
            [$request, $response, $body] = $refundFactory->handle($params);
            $refundModel->fill([
                'request_body' => $request,
                'response_body' => $response,
                'process_at' => date('Y-m-d H:i:s')
            ])->save();
            $orderModel->fill([
                'refund_at' => date('Y-m-d H:i:s'),
                'status' => OrderCode::STATUS_CANCEL,
                'pay_status' => OrderCode::PAY_STATUS_REFUND
            ])->save();

            return $orderModel;
        });

        return success();
    }

    /**
     * 获取应用ID
     * @return string
     */
    public static function getAppId():string
    {
        return (string)Context::get('appModel')->id;
    }

    /**
     * 物流轨迹
     * @return ResponseInterface
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Throwable
     */
    public function logistics():ResponseInterface
    {
        $input = $this->input(['logistics_code', 'logistics_no']);

        /** @var Invoice $invoiceModel */
        $invoiceModel = $this->invoiceServices
            ->where('logistics_no', $input['logistics_no'])
            ->where('logistics_code', $input['logistics_code'])
            ->firstOrFail();

        $response = $this->logisticServices->getInfo($invoiceModel->logistics_no, $invoiceModel->logistics_code, $invoiceModel->mobile);

        return success($response);
    }

}
